Scroll to navigation

SELECT(2) Руководство программиста Linux SELECT(2)

ИМЯ

select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - многопоточный синхронный ввод-вывод

ОБЗОР

/* В соответствие с POSIX.1-2001 */

#include <sys/select.h> /* В соответствие с более ранними стандартами */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set); #include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);


Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):

pselect(): _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600

ОПИСАНИЕ

Вызовы select() и pselect() позволяют программам отслеживать изменения нескольких файловых дескрипторов ожидая, когда один или более файловых дескрипторов станут "готовы" для операции ввода-вывода определённого типа (например, ввода). Файловый дескриптор считается готовым, если к нему возможно применить соответствующую операцию ввода-вывода (например, read(2)) без блокировки.

Работа select() и pselect() идентична за исключением трёх моментов:

(i)
В вызове select() время ожидания задаётся в структуре struct timeval (с секундами и микросекундами), а в pselect() используется структура struct timespec (с секундами и наносекундами).
(ii)
Вызов select() может обновить аргумент timeout, который показывает сколько времени прошло. Вызов pselect() не изменяет этот аргумент.
(iii)
Вызов select() не имеет аргумента sigmask, и ведёт себя также как pselect(), если при вызове было указано значение sigmask равное NULL.

Отслеживаются 3 независимых набора файловых дескрипторов. В тех, что перечислены в readfds, будет отслеживаться появление символов, доступных для чтения (говоря более точно, проверяется доступность чтения без блокировки; в частности, файловый дескриптор готов для чтения, если он указывает на конец файла); дескрипторы, указанные в writefds, будут отслеживаться для возможности записи без блокировки, а указанные в exceptfds, будут отслеживаться для обнаружения исключительных ситуаций. При возврате из вызова наборы изменяются, показывая какие файловые дескрипторы фактически изменили состояние. Значение любого из трёх наборов файловых дескрипторов может быть равно NULL, если слежение за определённым классом событий над файловыми дескрипторами не требуется.

Для манипуляций наборами существуют четыре макроса: FD_ZERO() очищает набор; FD_SET() добавляет заданный файловый дескриптор к набору; FD_CLR() удаляет файловый дескриптор из набора; FD_ISSET() проверяет, является ли файловый дескриптор частью набора. Эти макросы полезны после возврата из вызова select().

Значение nfds на единицу больше самого большого номера файлового дескриптора из всех трёх наборов.

В аргументе timeout задаётся минимальное количество времени, на которое заблокируется select() в ожидании готовности файлового дескриптора. Данное значение интервала будет округлено до точности системных часов, а из-за задержки при планировании в ядре блокирующий интервал будет немного больше. Если оба поля структуры timeval равны нулю, то select() завершится немедленно (полезно при опросе (polling)). Если значение timeout равно NULL (время ожидания не задано), то select() может блокировать работу неопределённо долго.

Значение sigmask является указателем на маску сигналов (смотрите sigprocmask(2)); если оно не равно NULL, то сначала pselect() заменяет текущую маску сигналов на заданную sigmask, затем выполняет функцию "select", после чего восстанавливает первоначальную сигнальную маску.

Кроме различия в точности аргумента timeout вызов pselect()


ready = pselect(nfds, &readfds, &writefds, &exceptfds,
timeout, &sigmask);
эквивалентен атомарному выполнению следующих вызовов:

sigset_t origmask;
sigprocmask(SIG_SETMASK, &sigmask, &origmask);
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
sigprocmask(SIG_SETMASK, &origmask, NULL);

Причина необходимости pselect() в том, что если нужно ждать какого-то сигнала или готовности файлового дескриптора, то необходимо атомарное тестирование для избежания состязательности. (Предположим, что обработчик сигнала устанавливает глобальный флаг и завершается. В этом случае тест этого глобального флага после вызова select() мог бы длиться бесконечно, если сигнал прибыл бы сразу после тестирования, но до вызова. В отличие от этого, pselect() позволяет сначала заблокировать сигналы, обработать уже поступившие и затем вызвать pselect() с желаемым значением sigmask, избегая состязательности.)

Время ожидания

Используемые структуры времени определены в <sys/time.h> и выглядят следующим образом:


struct timeval {

long tv_sec; /* секунды */
long tv_usec; /* микросекунды */ };

и


struct timespec {

long tv_sec; /* секунды */
long tv_nsec; /* наносекунды */ };

(Однако, смотрите ниже про версию POSIX 1003.1-2001.)

Иногда select() вызывается с пустыми наборами (всеми тремя), nfds равным нулю и непустым timeout для переносимой реализации перехода в режим ожидания (sleep) на периоды с точностью менее секунды.

В Linux вызов select() изменяет timeout для отражения времени, проведённого не в режиме ожидания; большая часть других реализаций этого не делает (согласно POSIX.1-2001 допускается любой из этих вариантов). Это вызывает проблемы как при переносе кода Linux, читающего timeout, на другие операционные системы, так и при переносе на Linux кода, использующего struct timeval для многократного вызова select() в цикле без его переинициализации. Во избежание этого следует считать, что значение timeout не определено после возврата из select().

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При успешном завершении select() и pselect() возвращают количество файловых дескрипторов, находящихся в наборах ( то есть, общее количество бит, установленных в readfds, writefds, exceptfds) при чём это количество может быть равным нулю, если время ожидания истекло, а интересующие события так и не произошли. При ошибке возвращается значение -1, а переменной errno присваивается соответствующий номер ошибки; наборы и значение timeout становятся неопределенными, поэтому при ошибке нельзя полагаться на их значение.

ОШИБКИ

В одном из наборов находится неверный файловый дескриптор. (Возможно файловый дескриптор уже закрыт, или при работе с ним произошла ошибка.)
При выполнении поступил сигнал; см. signal(7).
Значение nfds отрицательно или значение timeout некорректно.
Не удалось выделить память для внутренних таблиц.

ВЕРСИИ

Вызов pselect() был добавлен в ядро Linux версии 2.6.16. До этого pselect() эмулировался в glibc (но, см. ДЕФЕКТЫ).

СООТВЕТСТВИЕ СТАНДАРТАМ

Вызов select() соответствует POSIX.1-2001 и 4.4BSD (впервые select() появился в 4.2BSD). Обычно перенос выполняется с не-BSD систем и на них, если они поддерживают уровень BSD-сокетов (включая варианты System V). Однако, заметим, что вариант System V, обычно, устанавливает значение переменной timeout перед выходом, а вариант BSD - нет.

Вызов pselect() определён в стандарте POSIX.1g и в POSIX 1004.1-2001.

ЗАМЕЧАНИЯ

fd_set представляет собой буфер фиксированного размера. Выполнение FD_CLR() или FD_SET() с отрицательным значением fd, равным или большим чем FD_SETSIZE, приводит к неопределённому поведению. Более того, согласно POSIX fd должен быть корректным файловым дескриптором.

Что касается задействованных типов, классическим вариантом является структура timeval с двумя полями типа long (как показано ниже), которая определена в <sys/time.h>. В POSIX.1-2001:


struct timeval {

time_t tv_sec; /* секунды */
suseconds_t tv_usec; /* микросекунды */ };

где структура определена в <sys/select.h>, а типы данных time_t и suseconds_t определены в <sys/types.h>.

Что касается прототипов, классическим вариантом является объявление select() в <time.h>. Согласно POSIX.1-2001 объявления select() и pselect() должны включаться в <sys/select.h>.

В libc4 и libc5 нет заголовочного файла <sys/select.h>; в glibc 2.0 и более поздних он есть. В glibc 2.0 прототип pselect() ошибочно определен всегда. В glibc 2.1 до версии 2.2.1 pselect() определён при определённом _GNU_SOURCE. Требования, которые необходимы для работы с glibc начиная с версии 2.2.2, показаны в разделе ОБЗОР.

Замечания, касающиеся Linux

Интерфейс pselect(), описанный в этой странице, реализован в glibc. Для этого используется системный вызов pselect6(). Поведение данного системного вызова несколько отличается от обёрточной функции glibc.

В Linux системный вызов pselect6() изменяет содержимое своего аргумента timeout. Однако, обёрточная функция glibc скрывает это поведение используя локальную переменную для аргумента timeout при передаче в системный вызов. Таким образом, функция pselect() в glibc не изменяет свой аргумент timeout; это поведение требуется в POSIX.1-2001.

Последний аргумент системного вызова pselect6() не является указателем sigset_t *, он представляет собой структуру в виде:

struct {

const sigset_t *ss; /* указатель на набор сигналов */
size_t ss_len; /* размер (в байтах) объекта, на который
указывает «ss» */ };

Это позволяет системному вызову получить и указатель на набор сигналов, так как в большинстве архитектур системным вызовам можно передать максимум 6 аргументов.

ДЕФЕКТЫ

Glibc 2.0 предоставляет версию pselect(), которая не принимает аргумент sigmask.

Начиная с версии 2.1, glibc предоставляет эмуляцию pselect(), которая реализована с помощью sigprocmask(2) и select(). Эта реализация остаётся уязвимой к той самой состязательности, для устранения которой и был разработан pselect(). В современных версии glibc используется (бессостязательный) системный вызов pselect(), если он предоставляется ядром.

В системах без pselect(), надёжного (и более переносимого) перехвата сигнала можно достичь с помощью трюка с каналом в самого себя. В этом методе обработчик сигнала пишет байт в канал, чей второй конец отслеживается select() в основной программе (чтобы избежать возможной блокировки при записи в канал, который может быть заполнен, или при чтении из канала, который может быть пуст, нужно использовать неблокирующий ввод/вывод).

В Linux, вызов select() может сообщать о файловом дескрипторе сокета как о «готовом для чтения», хотя при последующем чтении произойдёт блокировка. Это может случиться, например, когда данные прибыли, но при анализе их контрольная сумма не совпала и они были отброшены. Также могут быть другие обстоятельства, при которых файловый дескриптор ошибочно считается готовым. Поэтому, возможно безопасней будет использовать для сокетов O_NONBLOCK, которые не должны блокироваться.

В Linux, вызов select() также изменяет timeout, если он прерван обработчиком сигнала (т.е., возвращается ошибка EINTR). Согласно POSIX.1-2001 это не разрешено. В Linux системный вызов pselect() действует также, но обёртка glibc скрывает это поведение копируя перед вызовом timeout в локальную переменную и передавая её в системный вызов.

ПРИМЕР

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void)
{

fd_set rfds;
struct timeval tv;
int retval;
/* Следить, когда в stdin (fd 0) что-нибудь появится. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Ждать не больше пяти секунд. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Больше не полагаться на значение tv! */
if (retval == -1)
perror("select()");
else if (retval)
printf("Есть данные.\n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("Данные не появились в течение пяти секунд.\n");
exit(EXIT_SUCCESS); }

СМОТРИТЕ ТАКЖЕ

Обсуждение и примеры смотрите в select_tut(2).

Неявно связанные темы описаны в accept(2), connect(2), poll(2), read(2), recv(2), send(2), sigprocmask(2), write(2), epoll(7), time(7)

2012-05-02 Linux